#include "WWCommonUtility.h"
#include "../Core/CommonUtility.hpp"

#include <vector>
#include <stdio.h>
#include <string.h>
#include <wchar.h>
#include <stdlib.h>
#include <string>
#include <iconv.h>
#include <boost/shared_array.hpp>
#include <boost/noncopyable.hpp>
#include <errno.h>
#include <openssl/md5.h>
#include <openssl/des.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <math.h>
#include <uuid/uuid.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>

#if defined (__APPLE__) || defined (__MACH__) 
#include <mach-o/dyld.h>
#endif

using std::string;
using std::wstring;

#define GBK_CODE_PAGE "GBK"
namespace wwcommon
{

using core::utility;

std::string CommonUtility::GenerateGuidStr()
{
    uuid_t out;
    uuid_generate(out);
    std::string result;
    for(int i=0; i < 16; i++)
    {
        char tmp[3] = {'\0'};
        snprintf(tmp, 3, "%2.2x", (unsigned char)out[i]);
        result += tmp;
    }
    return result;
}

// the base64 impletion of atl not work for mac osx, we need rewrite use openssl
void CommonUtility::Base64Encode(const unsigned char* in, int inLen, char* out, int& outLen)
{
    if(in == NULL)
    {
        assert(0);
        return;
    }
    BIO* bmem, *b64;
    BUF_MEM *bptr;
    b64 = BIO_new(BIO_f_base64());
    bmem = BIO_new(BIO_s_mem());
    b64 = BIO_push(b64, bmem);
    BIO_write(b64, in, inLen);
    if(BIO_flush(b64) != 1)
    {
        perror("bio_flush failed.");
        BIO_free_all(b64);
        return;
    }
    BIO_get_mem_ptr(b64, &bptr);

    if( (bptr->length > outLen) || (out == NULL) )
    {
        outLen = bptr->length;
        BIO_free_all(b64);
        return;
    }
    memcpy(out, bptr->data, bptr->length - 1);
    out[bptr->length - 1] = 0;
    outLen = bptr->length - 1;
    BIO_free_all(b64);
}
void CommonUtility::Base64Decode(const char* in, int inLen, unsigned char* out, int& outLen)
{
    if(in == NULL)
    {
        assert(0);
        return;
    }
    std::vector<char> tmp(inLen + 1);
    memcpy(&tmp[0], in, inLen);
    tmp[inLen] = '\n';
    memset(out, 0, outLen);
    BIO* b64 = BIO_new(BIO_f_base64());
    BIO* bmem = BIO_new_mem_buf(&tmp[0], inLen + 1 );
    bmem = BIO_push(b64, bmem);
    outLen = BIO_read(bmem, out, outLen);
    BIO_free_all(bmem);
}


std::string Md5Hash::GetMD5(const std::string& src)
{
    unsigned char md[16];
    MD5((const unsigned char*)src.data(), src.size(), md);
    return std::string((const char*)md, 16);
}
std::string Md5Hash::GetMD5FromFile(const std::string& filepath)
{
    int fd = open(filepath.c_str(), O_RDONLY);
    if(fd == -1)
        return "";
    unsigned char tempbuf[8092] = {0};
    int readed = 0;
    MD5_CTX  md5_state;
    MD5_Init(&md5_state);
    while((readed = read(fd, tempbuf, 8092)) != 0)
    {
        if(readed == -1)
        {
            perror("read file for md5 error.\n");
            close(fd);
            return "";
        }
        MD5_Update(&md5_state, tempbuf, readed);
    }
    unsigned char md[16];
    MD5_Final(md, &md5_state);
    close(fd);
    return std::string((const char*)md, 16);
}
std::string Md5Hash::ConvertMD5ToPrintable(const std::string& src)
{
    assert(src.size() == 16);
    std::string dest;
    for(int i = 0; i < 16; i++)
    {
        char tmp[3] = {'\0'};
        snprintf(tmp, 3, "%2.2x", (unsigned char)src[i]);
        dest += tmp;
    }
    assert(dest.size() == 32);
    return dest;
}

bool BinFile::Open(const std::string& filepath, int flag)
{
    Close();
    m_fd = open(filepath.c_str(), flag, S_IRUSR|S_IWUSR);
    if(m_fd == -1)
    {
        perror("open file error.");
        printf("file name:%s\n", filepath.c_str());
        assert(false);
        return false;
    }
    return true;
}

bool BinFile::CopyTo(const std::string& dest_filepath)
{
    if(m_fd == -1)
        return false;
    off_t curpos = lseek(m_fd, 0, SEEK_CUR);
    if(curpos == -1)
    {
        perror("seek current pos error.");
        return false;
    }

    int destfd = open(dest_filepath.c_str(), O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR);
    if(destfd == -1)
    {
        perror("open dest file for copy failed.");
        return false;
    }
    lseek(m_fd, 0, SEEK_SET);
    char tmpbuf[1024*8];
    int ret;
    while((ret = Read(tmpbuf, 1024*8)) > 0)
    {
        int wret = write(destfd, tmpbuf, ret);
        if(wret != ret)
        {
            perror("write file error while copy.");
            lseek(m_fd, curpos, SEEK_SET);
            close(destfd);
            return false;
        }
    }
    lseek(m_fd, curpos, SEEK_SET);
    close(destfd);
    if(ret == 0)
    {
        return true;
    }
    return false;
}

bool BinFile::Write(const void* buffer, size_t len)
{
    int wret = write(m_fd, buffer, len);
    if(wret != (int)len)
    {
        perror("write to file error");
        return false;
    }
    return true;
}
int BinFile::Read(void* buffer, size_t len)
{
    int readed;
    if( (readed = read(m_fd, buffer, len)) == -1)
    {
        perror("read file error.");
        return -1;
    }
    return readed;
}
off_t BinFile::GetLength()
{
    if (m_fd == -1)
    {
        return -1;
    }

    off_t curpos = lseek(m_fd, 0, SEEK_CUR);
    if (curpos == -1)
    {
        perror("seek current position error.");
        return -1;
    }
    off_t endpos = lseek(m_fd, 0, SEEK_END);
    curpos = lseek(m_fd, curpos, SEEK_SET);
    if (curpos == -1)
    {
        perror("set old current position error.");
    }
    return endpos;       
}
void BinFile::Close()
{
    if(m_fd == -1)
        return;
    close(m_fd);
    m_fd = -1;
}

std::string CDesEncryptMacCompatible::SetKey(const std::string& key)
{
    DES_string_to_key(key.c_str(), &m_key);
    return std::string((char*)m_key, 8);
}
std::string CDesEncryptMacCompatible::En_De_crypt(const std::string& data, int env)
{
    // we use 0 padding, so the data should not contain the 0.
    if(env == DES_ENCRYPT)
    {
        assert( std::string(data.c_str()).size() == data.size() );
    }
    DES_key_schedule schedule;
    DES_set_key((C_Block*)m_key, &schedule);
    std::string result;
    size_t len = data.size();
    const char* pdata = data.data();
    if(env == DES_DECRYPT)
    {
        assert( (len % 8) == 0 );
    }
    while(len >= 8)
    {
        DES_cblock out;
        memset(&out, 0, 8);
        DES_ecb_encrypt((const_DES_cblock*)pdata, &out, &schedule, env);
        result.append((const char*)&out, 8);
        pdata += 8;
        len -= 8;
    }
    if(len > 0)
    {
        DES_cblock temp, out;
        memset(&temp, 0, 8);
        memset(&out, 0, 8);
        memcpy(&temp, pdata, len);
        DES_ecb_encrypt(&temp, &out, &schedule, env);
        result.append((const char*)&out, 8);
    }
    return result;
}

bool CDesEncryptMacCompatible::En_De_CryptFile(const std::string& infilepath, const std::string& outfilepath, int env)
{
    DES_key_schedule schedule;
    DES_set_key((C_Block*)m_key, &schedule);
    BinFile outfile;
    BinFile infile;
    bool oret = outfile.Open(outfilepath, O_WRONLY|O_TRUNC|O_CREAT);
    bool iret = infile.Open(infilepath, O_RDONLY);
    if( !oret || !iret )
    {
        return false;
    }
    unsigned char tmp[8];
    int tmplen = 0;
    while((tmplen = infile.Read(tmp, 8)) == 8)
    {
        DES_cblock out;
        memset(&out, 0, 8);
        DES_ecb_encrypt((const_DES_cblock*)tmp, &out, &schedule, env);
        if(!outfile.Write((const void*)&out, 8))
        {
            return false;
        }
    }
    if(tmplen == -1)
    {
        return false;
    }
    if(tmplen > 0)
    {
        if(env == DES_DECRYPT)
            assert(0);
        DES_cblock temp, out;
        memset(&temp, 0, 8);
        memset(&out, 0, 8);
        memcpy(&temp, tmp, tmplen);
        DES_ecb_encrypt(&temp, &out, &schedule, env);
        if(!outfile.Write((const void*)&out, 8))
        {
            return false;
        }
    }
    return true;
}
}

